package cServer

import (
	"bytes"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"net/url"
	"strings"
	"time"

	"gitee.com/csingo/cError"
	"gitee.com/csingo/cHelper"

	"github.com/gin-contrib/cors"
	"github.com/gin-contrib/pprof"
	"github.com/gin-gonic/gin"

	"gitee.com/csingo/cCommon"
	"gitee.com/csingo/cContext"
	"gitee.com/csingo/cLog"
)

func newRouter() *gin.Engine {
	// 设置gin
	mode := cHelper.EnvToString("GIN_MODE", "")
	if mode == "" {
		if server_config.HttpServer.Debug {
			gin.SetMode(gin.DebugMode)
		} else {
			gin.SetMode(gin.ReleaseMode)
		}
	}

	router := gin.New()

	// 开启调试
	if server_config.HttpServer.Debug {
		pprof.Register(router)
	}

	// 开启跨域
	if server_config.HttpServer.Cross.Enable {
		if server_config.HttpServer.Cross.MaxAge <= 0 {
			server_config.HttpServer.Cross.MaxAge = 86400
		}
		if len(server_config.HttpServer.Cross.AllowMethods) == 0 {
			server_config.HttpServer.Cross.AllowMethods = []string{
				http.MethodGet,
				http.MethodHead,
				http.MethodPost,
				http.MethodPut,
				http.MethodPatch,
				http.MethodDelete,
				http.MethodConnect,
				http.MethodOptions,
				http.MethodTrace,
			}
		}
		headers := []string{
			"DNT",
			"Range",
			"Origin",
			"User-Agent",
			"Cache-Control",
			"Authorization",
			"Content-Type",
			"Content-Length",
			"Accept-Encoding",
			"X-Requested-With",
			"If-Modified-Since",
		}

		cross := cors.Config{
			AllowAllOrigins:  len(server_config.HttpServer.Cross.AllowOrigins) <= 0,
			AllowOrigins:     server_config.HttpServer.Cross.AllowOrigins,
			AllowMethods:     server_config.HttpServer.Cross.AllowMethods,
			AllowHeaders:     append(headers, server_config.HttpServer.Cross.AllowHeaders...),
			AllowCredentials: server_config.HttpServer.Cross.AllowCredentials,
			MaxAge:           time.Duration(server_config.HttpServer.Cross.MaxAge) * time.Second,
			AllowWildcard:    true,
			AllowWebSockets:  server_config.HttpServer.Cross.AllowWebSockets,
		}
		router.Use(cors.New(cross))
	}

	// panic recovery
	router.Use(gin.Recovery(), transferRequestData)

	// 基础权限校验
	if server_config.HttpServer.BasicAuthEnable {
		router.Use(gin.BasicAuth(server_config.HttpServer.BasicAuthUsers))
	}

	// 404 not found
	router.NoRoute(noRoute)

	// 注册路由
	routes := container.Routes()
	for _, item := range routes {
		switch item.Type {
		default:
			fallthrough
		case RouteTypeApi:
			handlers := append([]gin.HandlerFunc{}, item.Middlewares...)
			handlers = append(handlers, item.Handler)
			router.Match(item.Method, item.Path, handlers...)
		case RouteTypeFile:
			router.StaticFile(item.Path, item.Target)
		case RouteTypeDir:
			router.Static(item.Path, item.Target)
			router.StaticFile(item.Path, fmt.Sprintf("%s/index.html", strings.TrimRight(item.Target, "/")))
		case RouteTypeView:
			fs := container.GetView(item.Target)
			router.StaticFS(item.Path, fs)
			router.StaticFile(item.Path, fmt.Sprintf("%s/index.html", strings.TrimRight(item.Target, "/")))
		}
	}

	return router
}

func startHTTP() {
	if !server_config.HttpServer.Enable {
		return
	}

	svr := http.Server{
		Addr:           fmt.Sprintf(":%d", server_config.HttpServer.Port),
		Handler:        newRouter(),
		ReadTimeout:    time.Duration(server_config.HttpServer.ReadTimeout) * time.Second,
		WriteTimeout:   time.Duration(server_config.HttpServer.WriteTimeout) * time.Second,
		IdleTimeout:    time.Duration(server_config.HttpServer.IdleTimeout) * time.Second,
		MaxHeaderBytes: 1 << server_config.HttpServer.MaxHeaderBytes,
	}

	cLog.WithContext(nil, map[string]any{
		"source": "cServer.startHTTP",
		"port":   server_config.HttpServer.Port,
	}, []cLog.Option{
		cLog.IncludePaths{},
	}).Info("启动 HTTP 服务")
	err := svr.ListenAndServe()
	if err != nil {
		cLog.WithContext(nil, map[string]any{
			"source": "cServer.startHTTP",
			"config": server_config.HttpServer,
			"err":    err.Error(),
		}, []cLog.Option{
			cLog.IncludePaths{},
		}).Fatal("HTTP 服务启动失败")
	}
}

func transferRequestData(ctx *gin.Context) {
	var data []byte
	if ctx.Request.Body != nil {
		data, _ = io.ReadAll(ctx.Request.Body)
		ctx.Request.Body = io.NopCloser(bytes.NewBuffer(data))
	}
	traceid := cContext.GetTraceId(ctx)
	link, _ := url.Parse(ctx.Request.RequestURI)
	ctx.Set(cCommon.XIndex_RequestURI, link.Path)
	ctx.Set(cCommon.XIndex_QueryData, link.RawQuery)
	ctx.Set(cCommon.XIndex_RequestData, string(data))
	ctx.Set(cCommon.XIndex_RequestMethod, ctx.Request.Method)
	ctx.Set(cCommon.XIndex_RequestType, "HTTP")
	ctx.Set(cCommon.XIndex_TraceId, traceid)
	ctx.Header(cCommon.XIndex_TraceId, traceid)
}

func noRoute(ctx *gin.Context) {
	cLog.WithContext(ctx, map[string]interface{}{
		"source": "cServer.noRoute",
		"uri":    ctx.Request.RequestURI,
		"method": ctx.Request.Method,
	}, []cLog.Option{
		cLog.IncludePaths{},
	}).Error("404 NOT FOUND")
	ctx.AbortWithStatusJSON(http.StatusNotFound, gin.H{
		"code": cError.CError_Csingo_RouteNotFound.Code(),
		"msg":  "路由不存在",
		"data": map[string]string{
			"uri": ctx.Request.RequestURI,
		},
	})
}

func TestHTTP(req *http.Request) *httptest.ResponseRecorder {
	Initialize()

	router := newRouter()
	w := httptest.NewRecorder()
	router.ServeHTTP(w, req)

	return w
}
